package com.llj.pu;

import cn.hutool.core.util.ReflectUtil;
import com.llj.pu.annotation.ExcelColumn;
import com.llj.pu.model.ExcelBean;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.*;

/**
 * ClassName: ExcelPoiUtil
 * Description: Excel导出工具
 * author: @author l
 * time: 2025/6/30 5:07
 */
public final class ExcelPoiUtil {
    private static final ExcelPoiUtil INSTANCE = new ExcelPoiUtil();

    private ExcelPoiUtil() {
    }

    public static ExcelPoiUtil getInstance() {
        return INSTANCE;
    }

    /**
     * 导出Excel
     *
     * @param dataList 数据列表
     * @param <T>      数据类型
     * @return ByteArrayOutputStream
     */
    public <T extends ExcelBean> ByteArrayOutputStream export(List<T> dataList) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        if (Objects.isNull(dataList)) {
            throw new RuntimeException("The data can not be null.");
        }
        if (dataList.isEmpty()) {
            return os;
        }
        try (Workbook workbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.createSheet();
            T data = dataList.get(0);
            readHeader(sheet, data);
            fillDataList(sheet, dataList);
            workbook.write(os);
            return os;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private <T extends ExcelBean> void readHeader(Sheet sheet, T data) {
        List<Cell> leaves = new LinkedList<>();
        dfs(sheet, data, 0, 0, leaves);
        int rowNum = sheet.getLastRowNum();
        for (Cell leaf : leaves) {
            int rowIndex = leaf.getRowIndex();
            if (rowIndex < rowNum) {
                sheet.addMergedRegion(
                        new CellRangeAddress(
                                rowIndex,
                                rowNum,
                                leaf.getColumnIndex(),
                                leaf.getColumnIndex()
                        )
                );
            }
        }
    }

    private <T extends ExcelBean> int dfs(Sheet sheet, T data, int depth, int start, List<Cell> leaves) {
        Row row = sheet.getRow(depth) == null ? sheet.createRow(depth) : sheet.getRow(depth);
        Field[] fields = ReflectUtil.getFields(data.getClass());
        // 所有子集的长度
        int res = 0;
        int curr = start;
        for (Field field : fields) {
            ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
            if (Objects.nonNull(excelColumn)) {

                Cell cell = row.getCell(curr) == null ? row.createCell(curr) : row.getCell(curr);
                CellStyle cellStyle = sheet.getWorkbook().createCellStyle();
                cellStyle.setAlignment(HorizontalAlignment.CENTER);
                cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                cell.setCellStyle(cellStyle);
                cell.setCellValue(excelColumn.value());
                res = Math.max(res, excelColumn.ordering()) + 1;
                curr++;
                if (ExcelBean.class.isAssignableFrom(field.getType())) {
                    Object value = ReflectUtil.getFieldValue(data, field);
                    int maxOrder = dfs(sheet, (ExcelBean) value, depth + 1, curr - 1, leaves) - 1;
                    res += maxOrder;
                    curr += maxOrder;
                } else if (Collection.class.isAssignableFrom(field.getType())) {
                    Collection<?> value = (Collection<?>) ReflectUtil.getFieldValue(data, field);
                    if (value == null || value.isEmpty()) {
                        throw new RuntimeException("The collection can not be empty.");
                    }
                    Object o = value.stream().findFirst().get();
                    if (!ExcelBean.class.isAssignableFrom(o.getClass())) {
                        throw new RuntimeException("The collection element must be a subclass of ExcelBean.");
                    }
                    int maxOrder = dfs(sheet, (ExcelBean) o, depth + 1, curr - 1, leaves) - 1;
                    res += maxOrder;
                    curr += maxOrder;
                } else {
                    leaves.add(cell);
                }
                if (curr - cell.getColumnIndex() > 1) {
                    sheet.addMergedRegion(
                            new CellRangeAddress(
                                    cell.getRowIndex(),
                                    cell.getRowIndex(),
                                    cell.getColumnIndex(),
                                    curr - 1
                            )
                    );
                }
            }
        }

        return res;
    }

    private <T extends ExcelBean> void fillDataList(Sheet sheet, List<T> subList) {
        for (T t : subList) {
            int start = sheet.getLastRowNum() + 1;
            fillData(sheet, t, start, 0);
        }
    }

    private <T extends ExcelBean> int fillData(Sheet sheet, T data, int depth, int start) {
        Row row = sheet.getRow(depth) == null ? sheet.createRow(depth) : sheet.getRow(depth);
        Field[] fields = ReflectUtil.getFields(data.getClass());
        // 所有子集的长度
        int res = 0;
        int curr = start;
        int listMaxOrder = 0, listSize = 0, listFirstIndex = 0;
        for (Field field : fields) {
            ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
            if (Objects.nonNull(excelColumn)) {
                field.setAccessible(true);
                Object value = ReflectUtil.getFieldValue(data, field);

                Cell cell = row.getCell(curr) == null ? row.createCell(curr) : row.getCell(curr);
                CellStyle cellStyle = sheet.getWorkbook().createCellStyle();
                cellStyle.setAlignment(HorizontalAlignment.CENTER);
                cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                cell.setCellStyle(cellStyle);
                cell.setCellValue(String.valueOf(value));

                res = Math.max(res, excelColumn.ordering()) + 1;
                curr++;
                if (ExcelBean.class.isAssignableFrom(field.getType())) {
                    int maxOrder = fillData(sheet, (ExcelBean) value, depth, curr - 1) - 1;
                    res += maxOrder;
                    curr += maxOrder;
                } else if (Collection.class.isAssignableFrom(field.getType())) {
                    Collection<?> fieldValue = (Collection<?>) value;
                    if (fieldValue == null || fieldValue.isEmpty()) {
                        throw new RuntimeException("The collection can not be empty.");
                    }
                    Iterator<?> iterator = fieldValue.iterator();
                    Object o = iterator.next();
                    if (!ExcelBean.class.isAssignableFrom(o.getClass())) {
                        throw new RuntimeException("The collection element must be a subclass of ExcelBean.");
                    }
                    int maxOrder = fillData(sheet, (ExcelBean) o, depth, curr - 1) - 1;
                    res += maxOrder;
                    curr += maxOrder;
                    listMaxOrder = maxOrder;
                    listFirstIndex = curr - 1 - maxOrder;
                    while (iterator.hasNext()) {
                        Object o1 = iterator.next();
                        fillData(sheet, (ExcelBean) o1, depth + listSize + 1, listFirstIndex);
                        listSize++;
                    }
                }
            }
        }
        if (listSize > 0) {
            for (int i = start; i < start + res; i++) {
                if (i < listFirstIndex || i > listFirstIndex + listMaxOrder) {
                    sheet.addMergedRegion(
                            new CellRangeAddress(
                                    depth,
                                    depth + listSize,
                                    i,
                                    i
                            )
                    );
                }
            }
        }
        return res;
    }
}
